/*-------------------------------------------------------------------------
 *
 * dfmgr.c
 *	  动态函数管理器代码。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/fmgr/dfmgr.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/stat.h>

#ifdef HAVE_DLOPEN
#include <dlfcn.h>

/*
 * 在 macOS 上，<dlfcn.h> 坚持包含 <stdbool.h>。
 * 如果我们不使用 stdbool，取消定义 bool 以消除影响。
 */
#ifndef PG_USE_STDBOOL
#ifdef bool
#undef bool
#endif
#endif
#endif							/* HAVE_DLOPEN */

#include "fmgr.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "storage/shmem.h"
#include "utils/hsearch.h"


/* PostgreSQL 特定库初始化函数的签名 */
typedef void (*PG_init_t) (void);

/* rendezvous 变量的哈希表条目 */
typedef struct
{
	char		varName[NAMEDATALEN];	/* 哈希键（必须是第一个） */
	void	   *varValue;
} rendezvousHashEntry;

/*
 * 动态加载的文件列表（保存在malloc分配的内存中）。
 */

typedef struct df_files
{
	struct df_files *next;		/* 列表链接 */
	dev_t		device;			/* 设备文件开启 */
#ifndef WIN32					/* 确保我们不再依赖此项
								 * win32 */
	ino_t		inode;			/* 文件的 inode 号码 */
#endif
	void	   *handle;			/* pg_dl* 函数的句柄 */
	char		filename[FLEXIBLE_ARRAY_MEMBER];	/* 文件的完整路径名 */
} DynamicFileList;

static DynamicFileList *file_list = NULL;
static DynamicFileList *file_tail = NULL;

/* 在 Win32 下的 stat() 调用返回一个 st_ino 字段，但它没有意义 */
#ifndef WIN32
#define SAME_INODE(A,B) ((A).st_ino == (B).inode && (A).st_dev == (B).device)
#else
#define SAME_INODE(A,B) false
#endif

char	   *Dynamic_library_path;

static void *fc_internal_load_library(const char *fc_libname);
static void fc_incompatible_module_error(const char *fc_libname,
									  const Pg_magic_struct *fc_module_magic_data) pg_attribute_noreturn();
static bool fc_file_exists(const char *fc_name);
static char *fc_expand_dynamic_library_name(const char *fc_name);
static void fc_check_restricted_library_name(const char *fc_name);
static char *fc_substitute_libpath_macro(const char *fc_name);
static char *fc_find_in_dynamic_libpath(const char *fc_basename);

/* 模块需要匹配的魔术结构以便被接受 */
static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;


/*
 * 加载指定的动态链接库文件，并查找名为 funcname 的函数。
 *
 * 如果没有找到该函数，如果 signalNotFound 为真，我们会引发错误，
 * 否则返回 NULL。 请注意，加载库时的错误
 * 无论 signalNotFound 如何都将引发 ereport()。
 *
 * 如果 filehandle 不为 NULL，则 *filehandle 将被设置为识别库文件的句柄。
 * filehandle 可以与 lookup_external_function 一起使用，
 * 在同一文件中查找其他函数时比重复 load_external_function 成本更低。
 */
void * load_external_function(const char *fc_filename, const char *fc_funcname,
					   bool fc_signalNotFound, void **fc_filehandle)
{
	char	   *fc_fullname;
	void	   *fc_lib_handle;
	void	   *fc_retval;

	/* 扩展可能缩写的文件名以获得确切路径名 */
	fc_fullname = fc_expand_dynamic_library_name(fc_filename);

	/* 加载共享库，除非我们已经加载过 */
	fc_lib_handle = fc_internal_load_library(fc_fullname);

	/* 如果调用者需要，将返回句柄 */
	if (fc_filehandle)
		*fc_filehandle = fc_lib_handle;

	/* 在库中查找该函数。 */
	fc_retval = dlsym(fc_lib_handle, fc_funcname);

	if (fc_retval == NULL && fc_signalNotFound)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("could not find function \"%s\" in file \"%s\"",
						fc_funcname, fc_fullname)));

	pfree(fc_fullname);
	return fc_retval;
}

/*
 * 此函数加载一个 shlib 文件，而不查找其中的任何特定函数。
 * 如果之前已加载相同的 shlib，则卸载并重新加载它。
 *
 * 当'restricted'为真时，只能引用假定安全的
 * 目录 $libdir/plugins 中的库。
 */
void load_file(const char *fc_filename, bool fc_restricted)
{
	char	   *fc_fullname;

	/* 如果请求，应用安全限制 */
	if (fc_restricted)
		fc_check_restricted_library_name(fc_filename);

	/* 扩展可能缩写的文件名以获得确切路径名 */
	fc_fullname = fc_expand_dynamic_library_name(fc_filename);

	/* 加载共享库 */
	(void) fc_internal_load_library(fc_fullname);

	pfree(fc_fullname);
}

/*
 * 查找已加载库文件中的函数。
 * 如果未找到，则返回 NULL。
 */
void * lookup_external_function(void *fc_filehandle, const char *fc_funcname)
{
	return dlsym(fc_filehandle, fc_funcname);
}


/*
 * 加载指定的动态链接库文件，除非它已经
 * 被加载。 返回该文件的 pg_dl* 句柄。
 *
 * 注意：libname 期望是库文件的确切名称。
 *
 * 注意：目前没有办法卸载动态加载的文件。 我们可能会在将来
 * 添加一个，如果我们能说服自己拥有安全的协议来解除
 * 从钩子函数指针、释放自定义 GUC 变量，以及
 * 或许其他当前绝对不安全的事情。
 */
static void * fc_internal_load_library(const char *fc_libname)
{
	DynamicFileList *fc_file_scanner;
	PGModuleMagicFunction fc_magic_func;
	char	   *fc_load_error;
	struct stat fc_stat_buf;
	PG_init_t	PG_init;

	/*
	 * 扫描已加载的 FILES 列表以查看文件是否已被加载。
	 */
	for (fc_file_scanner = file_list;
		 fc_file_scanner != NULL &&
		 strcmp(fc_libname, fc_file_scanner->filename) != 0;
		 fc_file_scanner = fc_file_scanner->next)
		;

	if (fc_file_scanner == NULL)
	{
		/*
		 * 检查同一文件 - 不同路径（即，符号链接或链接）
		 */
		if (stat(fc_libname, &fc_stat_buf) == -1)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not access file \"%s\": %m",
							fc_libname)));

		for (fc_file_scanner = file_list;
			 fc_file_scanner != NULL &&
			 !SAME_INODE(fc_stat_buf, *fc_file_scanner);
			 fc_file_scanner = fc_file_scanner->next)
			;
	}

	if (fc_file_scanner == NULL)
	{
		/*
		 * 文件尚未加载。
		 */
		fc_file_scanner = (DynamicFileList *)
			malloc(offsetof(DynamicFileList, filename) + strlen(fc_libname) + 1);
		if (fc_file_scanner == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));

		MemSet(fc_file_scanner, 0, offsetof(DynamicFileList, filename));
		strcpy(fc_file_scanner->filename, fc_libname);
		fc_file_scanner->device = fc_stat_buf.st_dev;
#ifndef WIN32
		fc_file_scanner->inode = fc_stat_buf.st_ino;
#endif
		fc_file_scanner->next = NULL;

		fc_file_scanner->handle = dlopen(fc_file_scanner->filename, RTLD_NOW | RTLD_GLOBAL);
		if (fc_file_scanner->handle == NULL)
		{
			fc_load_error = dlerror();
			free((char *) fc_file_scanner);
			/* errcode_for_file_access 可能不适合这里？ */
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not load library \"%s\": %s",
							fc_libname, fc_load_error)));
		}

		/* 检查魔术函数以确定兼容性 */
		fc_magic_func = (PGModuleMagicFunction)
			dlsym(fc_file_scanner->handle, PG_MAGIC_FUNCTION_NAME_STRING);
		if (fc_magic_func)
		{
			const Pg_magic_struct *fc_magic_data_ptr = (*fc_magic_func) ();

			if (fc_magic_data_ptr->len != magic_data.len ||
				memcmp(fc_magic_data_ptr, &magic_data, magic_data.len) != 0)
			{
				/* 在取消链接库之前复制数据块 */
				Pg_magic_struct fc_module_magic_data = *fc_magic_data_ptr;

				/* 尝试关闭库 */
				dlclose(fc_file_scanner->handle);
				free((char *) fc_file_scanner);

				/* 提出合适的投诉 */
				fc_incompatible_module_error(fc_libname, &fc_module_magic_data);
			}
		}
		else
		{
			/* 尝试关闭库 */
			dlclose(fc_file_scanner->handle);
			free((char *) fc_file_scanner);
			/* complain */
			ereport(ERROR,
					(errmsg("incompatible library \"%s\": missing magic block",
							fc_libname),
					 errhint("Extension libraries are required to use the PG_MODULE_MAGIC macro.")));
		}

		/*
		 * 如果库中有 _PG_init() 函数，调用它。
		 */
		PG_init = (PG_init_t) dlsym(fc_file_scanner->handle, "_PG_init");
		if (PG_init)
			(*PG_init) ();

		/* 可以将其链接到列表中 */
		if (file_list == NULL)
			file_list = fc_file_scanner;
		else
			file_tail->next = fc_file_scanner;
		file_tail = fc_file_scanner;
	}

	return fc_file_scanner->handle;
}

/*
 * 对不兼容的魔术块报告适当的错误。
 */
static void fc_incompatible_module_error(const char *fc_libname,
						  const Pg_magic_struct *fc_module_magic_data)
{
	StringInfoData fc_details;

	/*
	 * 如果版本不匹配，只需报告这个，因为其余的
	 * 块可能甚至没有我们预期的字段。
	 */
	if (magic_data.version != fc_module_magic_data->version)
	{
		char		fc_library_version[32];

		if (fc_module_magic_data->version >= 1000)
			snprintf(fc_library_version, sizeof(fc_library_version), "%d",
					 fc_module_magic_data->version / 100);
		else
			snprintf(fc_library_version, sizeof(fc_library_version), "%d.%d",
					 fc_module_magic_data->version / 100,
					 fc_module_magic_data->version % 100);
		ereport(ERROR,
				(errmsg("incompatible library \"%s\": version mismatch",
						fc_libname),
				 errdetail("Server is version %d, library is version %s.",
						   magic_data.version / 100, fc_library_version)));
	}

	/*
	 * 类似地，如果ABI额外字段不匹配，则返回错误。其他
	 * 字段可能也会不匹配，但如果您完全使用错误的产品，
	 * 这不是有用的信息。
	 */
	if (strcmp(fc_module_magic_data->abi_extra, magic_data.abi_extra) != 0)
	{
		ereport(ERROR,
				(errmsg("incompatible library \"%s\": ABI mismatch",
						fc_libname),
				 errdetail("Server has ABI \"%s\", library has \"%s\".",
						   magic_data.abi_extra,
						   fc_module_magic_data->abi_extra)));
	}

	/*
	 * 否则，列出哪些字段不一致。
	 *
	 * XXX 每当魔法块中的字段集发生变化时，这段代码都必须进行调整！
	 */
	initStringInfo(&fc_details);

	if (fc_module_magic_data->funcmaxargs != magic_data.funcmaxargs)
	{
		if (fc_details.len)
			appendStringInfoChar(&fc_details, '\n');
		appendStringInfo(&fc_details,
						 _("Server has FUNC_MAX_ARGS = %d, library has %d."),
						 magic_data.funcmaxargs,
						 fc_module_magic_data->funcmaxargs);
	}
	if (fc_module_magic_data->indexmaxkeys != magic_data.indexmaxkeys)
	{
		if (fc_details.len)
			appendStringInfoChar(&fc_details, '\n');
		appendStringInfo(&fc_details,
						 _("Server has INDEX_MAX_KEYS = %d, library has %d."),
						 magic_data.indexmaxkeys,
						 fc_module_magic_data->indexmaxkeys);
	}
	if (fc_module_magic_data->namedatalen != magic_data.namedatalen)
	{
		if (fc_details.len)
			appendStringInfoChar(&fc_details, '\n');
		appendStringInfo(&fc_details,
						 _("Server has NAMEDATALEN = %d, library has %d."),
						 magic_data.namedatalen,
						 fc_module_magic_data->namedatalen);
	}
	if (fc_module_magic_data->float8byval != magic_data.float8byval)
	{
		if (fc_details.len)
			appendStringInfoChar(&fc_details, '\n');
		appendStringInfo(&fc_details,
						 _("Server has FLOAT8PASSBYVAL = %s, library has %s."),
						 magic_data.float8byval ? "true" : "false",
						 fc_module_magic_data->float8byval ? "true" : "false");
	}

	if (fc_details.len == 0)
		appendStringInfoString(&fc_details,
							   _("Magic block has unexpected length or padding difference."));

	ereport(ERROR,
			(errmsg("incompatible library \"%s\": magic block mismatch",
					fc_libname),
			 errdetail_internal("%s", fc_details.data)));
}

static bool fc_file_exists(const char *fc_name)
{
	struct stat fc_st;

	AssertArg(fc_name != NULL);

	if (stat(fc_name, &fc_st) == 0)
		return !S_ISDIR(fc_st.st_mode);
	else if (!(errno == ENOENT || errno == ENOTDIR || errno == EACCES))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not access file \"%s\": %m", fc_name)));

	return false;
}


/*
 * 如果名称包含斜杠，检查文件是否存在，如果存在则返回
 * 名称。否则（没有斜杠）尝试使用搜索路径进行扩展（参见
 * find_in_dynamic_libpath）；如果成功，返回完整
 * 扩展的文件名。如果之前失败，则附加DLSUFFIX并
 * 重新尝试。如果都失败，则只返回原始名称。
 *
 * 结果将始终是新分配的内存。
 */
static char * fc_expand_dynamic_library_name(const char *fc_name)
{
	bool		fc_have_slash;
	char	   *fc_new;
	char	   *fc_full;

	AssertArg(fc_name);

	fc_have_slash = (first_dir_separator(fc_name) != NULL);

	if (!fc_have_slash)
	{
		fc_full = fc_find_in_dynamic_libpath(fc_name);
		if (fc_full)
			return fc_full;
	}
	else
	{
		fc_full = fc_substitute_libpath_macro(fc_name);
		if (fc_file_exists(fc_full))
			return fc_full;
		pfree(fc_full);
	}

	fc_new = psprintf("%s%s", fc_name, DLSUFFIX);

	if (!fc_have_slash)
	{
		fc_full = fc_find_in_dynamic_libpath(fc_new);
		pfree(fc_new);
		if (fc_full)
			return fc_full;
	}
	else
	{
		fc_full = fc_substitute_libpath_macro(fc_new);
		pfree(fc_new);
		if (fc_file_exists(fc_full))
			return fc_full;
		pfree(fc_full);
	}

	/*
	 * 如果找不到文件，则直接返回该字符串。随后的
	 * 加载尝试将失败并报告适当的消息。
	 */
	return pstrdup(fc_name);
}

/*
 * 检查受限制的库名称。它必须以"$libdir/plugins/"
 * 开头，且之后不能有任何目录分隔符（这样就足以
 * 防止“..”风格的攻击）。
 */
static void fc_check_restricted_library_name(const char *fc_name)
{
	if (strncmp(fc_name, "$libdir/plugins/", 16) != 0 ||
		first_dir_separator(fc_name + 16) != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("access to library \"%s\" is not allowed",
						fc_name)));
}

/*
 * 替换给定字符串中出现的宏。
 * 结果始终是新分配的内存。
 */
static char * fc_substitute_libpath_macro(const char *fc_name)
{
	const char *fc_sep_ptr;

	AssertArg(fc_name != NULL);

	/* 目前，我们只识别字符串开头的$libdir */
	if (fc_name[0] != '$')
		return pstrdup(fc_name);

	if ((fc_sep_ptr = first_dir_separator(fc_name)) == NULL)
		fc_sep_ptr = fc_name + strlen(fc_name);

	if (strlen("$libdir") != fc_sep_ptr - fc_name ||
		strncmp(fc_name, "$libdir", strlen("$libdir")) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid macro name in dynamic library path: %s",
						fc_name)));

	return psprintf("%s%s", pkglib_path, fc_sep_ptr);
}


/*
 * 在以冒号分隔的搜索路径Dynamic_library_path中
 * 搜索名为'basename'的文件。如果找到该文件，将在新的
 * 分配内存中返回完整的文件名。如果未找到该文件，
 * 则返回NULL。
 */
static char * fc_find_in_dynamic_libpath(const char *fc_basename)
{
	const char *fc_p;
	size_t		fc_baselen;

	AssertArg(fc_basename != NULL);
	AssertArg(first_dir_separator(fc_basename) == NULL);
	AssertState(Dynamic_library_path != NULL);

	fc_p = Dynamic_library_path;
	if (strlen(fc_p) == 0)
		return NULL;

	fc_baselen = strlen(fc_basename);

	for (;;)
	{
		size_t		fc_len;
		char	   *fc_piece;
		char	   *fc_mangled;
		char	   *fc_full;

		fc_piece = first_path_var_separator(fc_p);
		if (fc_piece == fc_p)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_NAME),
					 errmsg("zero-length component in parameter \"dynamic_library_path\"")));

		if (fc_piece == NULL)
			fc_len = strlen(fc_p);
		else
			fc_len = fc_piece - fc_p;

		fc_piece = palloc(fc_len + 1);
		strlcpy(fc_piece, fc_p, fc_len + 1);

		fc_mangled = fc_substitute_libpath_macro(fc_piece);
		pfree(fc_piece);

		canonicalize_path(fc_mangled);

		/* 仅限绝对路径 */
		if (!is_absolute_path(fc_mangled))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_NAME),
					 errmsg("component in parameter \"dynamic_library_path\" is not an absolute path")));

		fc_full = palloc(strlen(fc_mangled) + 1 + fc_baselen + 1);
		sprintf(fc_full, "%s/%s", fc_mangled, fc_basename);
		pfree(fc_mangled);

		elog(DEBUG3, "find_in_dynamic_libpath: trying \"%s\"", fc_full);

		if (fc_file_exists(fc_full))
			return fc_full;

		pfree(fc_full);

		if (fc_p[fc_len] == '\0')
			break;
		else
			fc_p += fc_len + 1;
	}

	return NULL;
}


/*
 * 查找（或创建）一个可以让一个动态加载的
 * 库与另一个库相遇的“约定变量”。
 *
 * 在对特定varName的第一次调用中，将创建一个
 * 具有给定名称的“约定变量”。该变量的值是一个
 * void指针（初始设置为NULL）。后续对相同
 * varName的调用将返回现有变量的地址。一旦创建，
 * 约定变量将持续至进程的生命周期。
 *
 * 动态加载的库可以使用约定变量来找到彼此并共享信息：
 * 他们只需就变量名称和它将指向的数据达成一致。
 */
void	  **
find_rendezvous_variable(const char *fc_varName)
{
	static HTAB *fc_rendezvousHash = NULL;

	rendezvousHashEntry *fc_hentry;
	bool		fc_found;

	/* 如果我们尚未在此进程中创建哈希表，则创建一个 */
	if (fc_rendezvousHash == NULL)
	{
		HASHCTL		fc_ctl;

		fc_ctl.keysize = NAMEDATALEN;
		fc_ctl.entrysize = sizeof(rendezvousHashEntry);
		fc_rendezvousHash = hash_create("Rendezvous variable hash",
									 16,
									 &fc_ctl,
									 HASH_ELEM | HASH_STRINGS);
	}

	/* 查找或创建此varName的哈希表条目 */
	fc_hentry = (rendezvousHashEntry *) hash_search(fc_rendezvousHash,
												 fc_varName,
												 HASH_ENTER,
												 &fc_found);

	/* 如果是第一次，则初始化为NULL */
	if (!fc_found)
		fc_hentry->varValue = NULL;

	return &fc_hentry->varValue;
}

/*
 * 估算序列化我们已经加载的库列表所需的空间量。
 */
Size EstimateLibraryStateSpace(void)
{
	DynamicFileList *fc_file_scanner;
	Size		fc_size = 1;

	for (fc_file_scanner = file_list;
		 fc_file_scanner != NULL;
		 fc_file_scanner = fc_file_scanner->next)
		fc_size = add_size(fc_size, strlen(fc_file_scanner->filename) + 1);

	return fc_size;
}

/*
 * 将我们已加载的库列表序列化到一块内存中。
 */
void SerializeLibraryState(Size fc_maxsize, char *fc_start_address)
{
	DynamicFileList *fc_file_scanner;

	for (fc_file_scanner = file_list;
		 fc_file_scanner != NULL;
		 fc_file_scanner = fc_file_scanner->next)
	{
		Size		fc_len;

		fc_len = strlcpy(fc_start_address, fc_file_scanner->filename, fc_maxsize) + 1;
		Assert(fc_len < fc_maxsize);
		fc_maxsize -= fc_len;
		fc_start_address += fc_len;
	}
	fc_start_address[0] = '\0';
}

/*
 * 加载序列化后端加载的每个库。
 */
void RestoreLibraryState(char *fc_start_address)
{
	while (*fc_start_address != '\0')
	{
		fc_internal_load_library(fc_start_address);
		fc_start_address += strlen(fc_start_address) + 1;
	}
}
